package com.unrarme.morphology.morpher
{
	public class RussianName
	{
		private var type:String;
		
		public var sexM:String = 'm';
		public var sexF:String = 'f';
		public var gcaseIm:String =    'nominative';
		public var gcaseNom:String =  'nominative';      // именительный
		public var gcaseRod:String =   'genitive';        
		public var gcaseGen:String =  'genitive';        // родительный
		public var gcaseDat:String =   'dative';                                       // дательный
		public var gcaseVin:String =   'accusative';      
		public var gcaseAcc:String =  'accusative';      // винительный
		public var gcaseTvor:String =  'instrumentative'; 
		public var gcaseIns:String =  'instrumentative'; // творительный
		public var gcasePred:String =  'prepositional';   
		public var gcasePos:String =  'prepositional';   // предложный
		
		private var rules = {
			lastName: {
				exceptions: [
					'дюма,тома,дега,люка,ферма,гамарра,петипа,шандра|. . . . .',
					'гусь,ремень,камень,онук,богода,нечипас,долгопалец,маненок,рева,кива|. . . . .',
					'вий,сой,цой,хой|-я -ю -я -ем -е'
				],
				suffixes: [
					'f|б,в,г,д,ж,з,й,к,л,м,н,п,р,с,т,ф,х,ц,ч,ш,щ,ъ,ь|. . . . .',
					'f|ска,цка|-ой -ой -ую -ой -ой',
					'f|ая|--ой --ой --ую --ой --ой',
					'ская|--ой --ой --ую --ой --ой',
					'f|на|-ой -ой -у -ой -ой',
					
					'иной|-я -ю -я -ем -е',
					'уй|-я -ю -я -ем -е',
					'ца|-ы -е -у -ей -е',
					
					'рих|а у а ом е',
					
					'ия|. . . . .',
					'иа,аа,оа,уа,ыа,еа,юа,эа|. . . . .',
					'их,ых|. . . . .',
					'о,е,э,и,ы,у,ю|. . . . .',
					
					'ова,ева|-ой -ой -у -ой -ой',
					'га,ка,ха,ча,ща,жа|-и -е -у -ой -е',
					'ца|-и -е -у -ей -е',
					'а|-ы -е -у -ой -е',
					
					'ь|-я -ю -я -ем -е',
					
					'ия|-и -и -ю -ей -и',
					'я|-и -е -ю -ей -е',
					'ей|-я -ю -я -ем -е',
					
					'ян,ан,йн|а у а ом е',
					
					'ынец,обец|--ца --цу --ца --цем --це',
					'онец,овец|--ца --цу --ца --цом --це',
					
					'ц,ч,ш,щ|а у а ем е',
					
					'ай|-я -ю -я -ем -е',
					'гой,кой|-го -му -го --им -м',
					'ой|-го -му -го --ым -м',
					'ах,ив|а у а ом е',
					
					'ший,щий,жий,ний|--его --ему --его -м --ем',
					'кий,ый|--ого --ому --ого -м --ом',
					'ий|-я -ю -я -ем -и',
					
					'ок|--ка --ку --ка --ком --ке',
					'ец|--ца --цу --ца --цом --це',
					
					'в,н|а у а ым е',
					'б,г,д,ж,з,к,л,м,п,р,с,т,ф,х|а у а ом е'
				]
			},
			firstName: {
				exceptions: [
					'лев|--ьва --ьву --ьва --ьвом --ьве',
					'павел|--ла --лу --ла --лом --ле',
					'm|шота|. . . . .',
					'f|рашель,нинель,николь,габриэль,даниэль|. . . . .'
				],
				suffixes: [
					'е,ё,и,о,у,ы,э,ю|. . . . .',
					'f|б,в,г,д,ж,з,й,к,л,м,н,п,р,с,т,ф,х,ц,ч,ш,щ,ъ|. . . . .',
					
					'f|ь|-и -и . ю -и',
					'm|ь|-я -ю -я -ем -е',
					
					'га,ка,ха,ча,ща,жа|-и -е -у -ой -е',
					'а|-ы -е -у -ой -е',
					'ия|-и -и -ю -ей -и',
					'я|-и -е -ю -ей -е',
					'ей|-я -ю -я -ем -е',
					'ий|-я -ю -я -ем -и',
					'й|-я -ю -я -ем -е',
					'б,в,г,д,ж,з,к,л,м,н,п,р,с,т,ф,х,ц,ч|а у а ом е'
				]
			},
			middleName: {
				suffixes: [
					'ич|а у а ем е',
					'на|-ы -е -у -ой -е'
				]
			}
		};
		
		private var initialized:Boolean = false;
		
		public function RussianName(type:String = 'internal')
		{
			this.type = type;
		}
		
		private function prepareRules():void {
			for (var type:* in this.rules) {
				for(var key:* in this.rules[type]) {
					for(var i:Number = 0, n:Number = this.rules[type][key].length; i < n; i++) {
						this.rules[type][key][i] = this.rule(this.rules[type][key][i]);
					}
				}
			}
		}
		
		private function init():void {
			if(this.initialized) return;
			this.prepareRules();
			this.initialized = true;
		}
		
		private function rule(rule:String):Object {
			var r:Array = rule.split('|');
			if(r.length == 3) {
				return {sex: r[0], test: r[1].split(','), mods: r[2].split(' ')}
			} else if (r.length == 2) {
				return {sex: '', test: r[0].split(','), mods: r[1].split(' ')}
			}
			trace(rule);
			return false;
			
		}
		
		private function word(word:String, sex:String, wordType:String, gcase:String):String {
			// исходное слово находится в именительном падеже
			if(gcase == this.gcaseNom) return word;
			
			// составные слова
			if(word.match(/[-]/)) {
				var list:Array = word.split('-');
				for(var i:Number = 0; i < list.length; i++) {
					list[i] = this.word(list[i], sex, wordType, gcase);
				}
				return list.join('-');
			}
			
			// Иванов И. И.
			if(word.match(/^[А-ЯЁ]\.?$/i)) return word;
			
			this.init();
			var rules = this.rules[wordType];
			
			if(rules.exceptions) {
				var pick = this.pick(word, sex, gcase, rules.exceptions, true);
				if(pick) return pick;
			}
			var pick = this.pick(word, sex, gcase, rules.suffixes, false);
			return pick || word;
		}
		
		private function pick(word:String, sex:String, gcase:String, rules:Array, matchWholeWord:Boolean):* {
			var wordLower:String = word.toLowerCase();
			for(var i = 0; i < rules.length; i++) {
				//trace(i + ';' + rules.length + ';' + wordLower + ';' + sex + ';' + rules[i]+ ';' + matchWholeWord);
				if(this.ruleMatch(wordLower, sex, rules[i], matchWholeWord)) {
					return this.applyMod(word, gcase, rules[i]);
				}
			}
			return false;
		}
		
		private function ruleMatch(word:String, sex:String, rule:Object, matchWholeWord:Boolean):Boolean {
			if(rule.sex == this.sexM && sex == this.sexF) return false; // male by default
			if(rule.sex == this.sexF && sex != this.sexF) return false;
			for(var i = 0, n = rule.test.length; i < n; i++) {
				var test = matchWholeWord ? word : word.substr(Math.max(word.length - rule.test[i].length, 0));
				if(test == rule.test[i]) return true;
			}
			return false;
		}
		
		private function applyMod(word:String, gcase:String, rule:Object):String {
			var mod:String;
			switch(gcase) {
				case this.gcaseNom: mod = '.'; break;
				case this.gcaseGen: mod = rule.mods[0]; break;
				case this.gcaseDat: mod = rule.mods[1]; break;
				case this.gcaseAcc: mod = rule.mods[2]; break;
				case this.gcaseIns: mod = rule.mods[3]; break;
				case this.gcasePos: mod = rule.mods[4]; break;
				default: throw "Unknown grammatic case: "+gcase;
			}
			for(var i = 0, n = mod.length; i < n; i++) {
				var c = mod.substr(i, 1);
				switch(c) {
					case '.': break;
					case '-': word = word.substr(0, word.length - 1); break;
					default: word += c;
				}
			}
			return word;
		}
		
		private var ln:String;
		private var fn:String;
		private var mn:String;
		private var sex:String;
		private var fullNameSurnameLast:Boolean = false;
		
		public function morph(lastName:String, firstName:String = null, middleName:String = null, sex:String = null):String {
			if(firstName == null) {
				var m:Object = lastName.match(/^\s*(\S+)(\s+(\S+)(\s+(\S+))?)?\s*$/);
				if(!m) throw "Cannot parse supplied name";
				if(m[5] && m[3].match(/(ич|на)$/) && !m[5].match(/(ич|на)$/)) {
					// Иван Петрович Сидоров
					lastName = m[5];
					firstName = m[1];
					middleName = m[3];
					this.fullNameSurnameLast = true;
				} else {
					// Сидоров Иван Петрович
					lastName = m[1];
					firstName = m[3];
					middleName = m[5];
				}
			}
			this.ln = lastName;
			this.fn = firstName || '';
			this.mn = middleName || '';
			this.sex = sex || this.getSex();
			return '';
		}
		
		private function getSex():String {
			if(this.mn.length > 2) {
				switch(this.mn.substr(this.mn.length - 2)) {
					case 'ич': return this.sexM;
					case 'на': return this.sexF;
				}
			}
			return '';
		}
		
		public function fullName(gcase:String):String {
			return (
				(this.fullNameSurnameLast ? '' : this.lastName(gcase) + ' ')
				+ this.firstName(gcase) + ' ' + this.middleName(gcase) +
				(this.fullNameSurnameLast ? ' ' + this.lastName(gcase) : '')
			).replace(/^ +| +$/g, '');
		}
		
		public function lastName(gcase:String):String {
			var result:* = this.word(this.ln, this.sex, 'lastName', gcase);
			if(result != 'false') {
				return result;
			} else {
				return '';
			}
		}
		
		public function firstName(gcase:String):String {
			var result:* = this.word(this.fn, this.sex, 'firstName', gcase);
			if(result != 'false') {
				return result;
			} else {
				return '';
			}
		}
		
		public function middleName(gcase:String):String {
			var result:* = this.word(this.mn, this.sex, 'middleName', gcase);
			if(result != 'false') {
				return result;
			} else {
				return '';
			}
		}
		
	}
}